/**
 * Copyright (c) 2005-2009 springside.org.cn
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * 
 * $Id: HibernateDao.java 763 2009-12-27 18:36:21Z calvinxiu $
 */
package net.uman.framework.modules.orm.hibernate;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.uman.entity.IdEntity;
import net.uman.framework.modules.objectpool.KeyGenerator;
import net.uman.framework.modules.objectpool.ObjectPool;
import net.uman.framework.modules.objectpool.ObjectPoolFactory;
import net.uman.framework.modules.orm.Page;
import net.uman.framework.modules.orm.PropertyFilter;
import net.uman.framework.modules.utils.SqlHelper;
import net.uman.util.IdCode;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.ProjectionList;
import org.springframework.util.Assert;

/**
 * 封装SpringSide扩展功能的Hibernat DAO泛型基类.
 * 
 * 扩展功能包括分页查询,按属性过滤条件列表查询.
 * 可在Service层直接使用,也可以扩展泛型DAO子类使用,见两个构造函数的注释.
 * 
 * @param <T> DAO操作的对象类型
 * @param <PK> 主键类型
 * 
 * @author calvin
 */
public class HibernateDao<T extends IdEntity, PK extends Serializable> extends SimpleHibernateDao<T, PK> {
	private int PAGE_LIFE = new Integer(System.getProperty("objectpool.pageLife", "3"));	// 缺省3分钟
	private int PAGE_FETCH_NUMBER = 3;		// 每次读取的页数
	private int PAGE_CACHE_NUMBER = 10;		// 可以cache时每次读取的页数
	
	/**
	 * 用于Dao层子类使用的构造函数.
	 * 通过子类的泛型定义取得对象类型Class.
	 * eg.
	 * public class UserDao extends HibernateDao<User, Long>{
	 * }
	 */
	public HibernateDao() {
		super();
	}

	/**
	 * 用于省略Dao层, Service层直接使用通用HibernateDao的构造函数.
	 * 在构造函数中定义对象类型Class.
	 * eg.
	 * HibernateDao<User, Long> userDao = new HibernateDao<User, Long>(sessionFactory, User.class);
	 */
	public HibernateDao(final Class<T> entityClass) {
		super(entityClass);
	}

    public T initialize(T obj) {
        if (obj != null && !Hibernate.isInitialized(obj)) {
            obj = get((PK)new Long(obj.getId()));
        }
        return obj;
    }
	
	//-- 分页查询函数 --//
	/**
	 * 分页获取全部对象.
	 */
	public <X> Page<X> getAll(final Page<X> page) {
		return findPage(page);
	}

	public int delete(List<PropertyFilter> filters) {
		HQLGenerator g = new HQLGenerator(entityClass, filters);
		g.process();
		String hql = g.getDeleteHql();
		Map<String, Object> values = g.getValues();
		int count = delete(hql, values);
		g.dispose();
		return count;
	}

	public <X> Page<X> findPageByNamedQuery(final Page<X> page, final String hqlName, final Object... values) {
		String hql = SqlHelper.getSql(hqlName);
		boolean nativeSql = SqlHelper.isNativeSql(hqlName);
		return findPage(page, nativeSql, hql, values);
	}
	
	/**
	 * 按HQL分页查询.
	 * 
	 * @param page 分页参数.不支持其中的orderBy参数.
	 * @param hql hql语句.
	 * @param values 数量可变的查询参数,按顺序绑定.
	 * 
	 * @return 分页查询结果, 附带结果列表及所有查询时的参数.
	 */
	public <X> Page<X> findPage(final Page<X> page, final String hql, final Object... values) {
		return findPage(page, false, hql, values);
	}
	
	public <X> Page<X> findPageByNativeSql(final Page<X> page, final String hql, final Object... values) {
		return findPage(page, true, hql, values);
	}
	
	@SuppressWarnings("unchecked")
	private <X> Page<X> findPage(final Page<X> page, boolean nativeSql, final String hql, final Object... values) {
		Assert.notNull(page, "page不能为空");

		ObjectPool pool = ObjectPoolFactory.getInstance();
		String key = KeyGenerator.getKey(page, hql, values);
		Page<X> _page = (Page<X>)pool.get(key);
		if (_page != null) {
			List<X> result = getCachedResult(_page, page);
			if (result != null) {
				page.setResult(result);
				pool.set(key, _page, ObjectPool.ONE_MINUTE * PAGE_LIFE);  // renew the pool
				return page;
			}
		}
		
		Query q = createQuery(nativeSql, hql, values);
	
		setPageParameter(q, page);
		List result = q.list();

		postProcess(page, result, pool, key);

		return page;
	}

	public <X> Page<X> findPageByNamedQuery(final Page<X> page, final String hqlName, final Map<String, ?> values) {
		String hql = SqlHelper.getSql(hqlName);
		boolean nativeSql = SqlHelper.isNativeSql(hqlName);
		return findPage(page, nativeSql, hql, values);
	}
	
	/**
	 * 按HQL分页查询.
	 * 
	 * @param page 分页参数.
	 * @param hql hql语句.
	 * @param values 命名参数,按名称绑定.
	 * 
	 * @return 分页查询结果, 附带结果列表及所有查询时的参数.
	 */
	@SuppressWarnings("unchecked")
	public <X> Page<X> findPage(final Page<X> page, final String hql, final Map<String, ?> values) {
		return findPage(page, false, hql, values);
	}
	
	public <X> Page<X> findPageByNativeSql(final Page<X> page, final String hql, final Map<String, ?> values) {
		return findPage(page, true, hql, values);
	}
	
	private <X> Page<X> findPage(final Page<X> page, boolean nativeSql, final String hql, final Map<String, ?> values) {
		Assert.notNull(page, "page不能为空");

		ObjectPool pool = ObjectPoolFactory.getInstance();
		String key = KeyGenerator.getKey(page, hql, values);
		Page<X> _page = null;
		if (page.isCachable()) {
			_page = (Page<X>)pool.get(key);
		}
		if (_page != null) {
			List<X> result = getCachedResult(_page, page);
			if (result != null) {
				page.setResult(result);
				pool.set(key, _page, ObjectPool.ONE_MINUTE * PAGE_LIFE);  // renew the pool
				return page;
			}
		}
		
		Query q = createQuery(nativeSql, hql, values);

		setPageParameter(q, page);
		List result = q.list();

		postProcess(page, result, pool, key);

		return page;
	}

	private <X> List<X> getCachedResult(Page<X> cache, Page<X> page) {
		if (cache == null) {
			return null;
		}
		
		// page.getPageNo() starts from 1
		int pageStart = (page.getPageNo() - 1) * page.getPageSize();
		if (page.getPageNo() >= cache.getCachePageNo() && pageStart < cache.getTotalCount()) {
			if (page.getPageNo() >= cache.getCachePageNo() + PAGE_CACHE_NUMBER - PAGE_FETCH_NUMBER - 1) {
				// we need to know if next 2 pages are available
				if (cache.getTotalCount() % page.getPageSize() == 0) {
					// should re-read data
					return null;
				}
			}
			List<X> cacheResult = cache.getCacheResult();
			int cacheStart = (cache.getCachePageNo() - 1) * page.getPageSize();
			List<X> result = new ArrayList(cacheResult.subList(pageStart - cacheStart, pageStart - cacheStart + (int)Math.min(cache.getTotalCount() - pageStart, page.getPageSize())));
			
			page.setTotalCount(pageStart + (int)Math.min(cache.getTotalCount() - pageStart, PAGE_FETCH_NUMBER * page.getPageSize()));
			return result;
		}
		
		return null;
	}
	
	public <X> Page<X> findPage(final Page<X> page, final Criterion... criterions) {
		return findPage(page, PropertyFilter.RelationType.AND, criterions);
	}
	/**
	 * 按Criteria分页查询.
	 * 
	 * @param page 分页参数.
	 * @param criterions 数量可变的Criterion.
	 * 
	 * @return 分页查询结果.附带结果列表及所有查询时的参数.
	 */
	@SuppressWarnings("unchecked")
	public <X> Page<X> findPage(final Page<X> page, PropertyFilter.RelationType rel, final Criterion... criterions) {
		Assert.notNull(page, "page不能为空");

		ObjectPool pool = ObjectPoolFactory.getInstance();
		String key = KeyGenerator.getKey(entityClass, page, rel, criterions);
		Page<X> _page = (Page<X>)pool.get(key);
		if (_page != null) {
			List<X> result = getCachedResult(_page, page);
			if (result != null) {
				page.setResult(result);
				pool.set(key, _page, ObjectPool.ONE_MINUTE * PAGE_LIFE);  // renew the pool
				return page;
			}
		}

		Criteria c = createCriteria(rel, criterions);
		
		setPageParameter(c, page);
		List result = c.list();
	
		postProcess(page, result, pool, key);
		
		return page;
	}
	
	private <X> void postProcess(final Page<X> page, List result, ObjectPool pool, String key) {
		// page.getPageNo() starts from 1
		int pageStart = (page.getPageNo() - 1) * page.getPageSize();
		
		if (result.size() > page.getPageSize()) {
			if (page.isCachable() && pool.isEnable()) {
				page.setCacheResult(result);
				page.setCachePageNo(page.getPageNo());
				page.setMaybeHasMore(page.getPageSize() <= result.size());
				page.setTotalCount(pageStart + result.size());
				
				pool.set(key, page, ObjectPool.ONE_MINUTE * PAGE_LIFE);
			}
			// PAGE_FETCH_NUMBER pages in all cases  
			page.setTotalCount(pageStart + Math.min(result.size(), PAGE_FETCH_NUMBER * page.getPageSize()));
			page.setResult(new ArrayList(result.subList(0, page.getPageSize())));
		} else {
			// only 1 page, need not cache it
			page.setResult(result);
			page.setTotalCount(pageStart + result.size());
		}		
	}
	
	/**
	 * 添加ProjectionList,完成
	 * @author haocheng
	 * @param <X>
	 * @param page
	 * @param rel
	 * @param projections
	 * @param criterions
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <X> Page<X> findPage(final Page<X> page, final ProjectionList projections,final Criterion... criterions) {
		Assert.notNull(page, "page不能为空");

		Criteria c = createCriteria(PropertyFilter.RelationType.AND, criterions);	
		c.setProjection(projections);
	
		setPageParameter(c, page);
		List result = c.list();

		// page.getPageNo() starts from 1
		int pageStart = (page.getPageNo() - 1) * page.getPageSize();
		page.setTotalCount(pageStart + result.size());
		
		if (result.size() > page.getPageSize()) { 
			page.setResult(new ArrayList(result.subList(0, page.getPageSize())));
		} else {
			// only 1 page, need not cache it
			page.setResult(result);
		}
		
		return page;
	}
	
	/**
	 * 设置分页参数到Query对象,辅助函数.
	 */
	protected Query setPageParameter(final Query q, final Page page) {
		//hibernate的firstResult的序号从0开始
		q.setFirstResult(page.getFirst() - 1);
		if (page.isCachable() && ObjectPoolFactory.getInstance().isEnable()) {
			// get PAGE_CACHE_NUMBER pages
			q.setMaxResults(page.getPageSize() * PAGE_CACHE_NUMBER);
		} else {
			// get PAGE_FETCH_NUMBER pages
			q.setMaxResults(page.getPageSize() * PAGE_FETCH_NUMBER);
		}
		return q;
	}

	/**
	 * 设置分页参数到Criteria对象,辅助函数.
	 */
	protected Criteria setPageParameter(final Criteria c, final Page page) {
		//hibernate的firstResult的序号从0开始
		c.setFirstResult(page.getFirst() - 1);
		if (page.isCachable() && ObjectPoolFactory.getInstance().isEnable()) {
			// get PAGE_CACHE_NUMBER pages
			c.setMaxResults(page.getPageSize() * PAGE_CACHE_NUMBER);
		} else {
			// get PAGE_FETCH_NUMBER pages
			c.setMaxResults(page.getPageSize() * PAGE_FETCH_NUMBER);
		}
		
		if (page.isOrderBySetted()) {
			String[] orderByArray = StringUtils.split(page.getOrderBy(), ',');
			String[] orderArray = StringUtils.split(page.getOrder(), ',');

			Assert.isTrue(orderByArray.length == orderArray.length, "分页多重排序参数中,排序字段与排序方向的个数不相等");

			for (int i = 0; i < orderByArray.length; i++) {
				if (Page.ASC.equals(orderArray[i])) {
					c.addOrder(Order.asc(orderByArray[i]));
				} else {
					c.addOrder(Order.desc(orderByArray[i]));
				}
			}
		}
		return c;
	}


	public Object findUnique(List<PropertyFilter> filters) {
		return findUnique(null, filters);
	}
	
	public Object findUnique(List<String> items, List<PropertyFilter> filters) {
		List list = find(items, filters);
		if (list == null || list.size() == 0) {
			return null;
		}
		return list.get(0);
	}
	/**
	 * 按属性过滤条件列表查找对象列表.
	 */
	public <X> List<X> find(List<PropertyFilter> filters) {
		return find(null, filters);
	}
	
	public <X> List<X> find(List<String> items, List<PropertyFilter> filters) {
		HQLGenerator g = new HQLGenerator(items, entityClass, filters);
		g.process();
		String hql = g.getHql();
		Map<String, Object> values = g.getValues();
		try {
			return find(hql, values);
		} finally {
			g.dispose();
		}
	}

	public List<T> find(List<String> items, net.uman.framework.modules.search.Criteria criteria) {
		HQLGenerator g = new HQLGenerator(items, entityClass, criteria);
		g.process();
		String hql = g.getHql();
		Map<String, Object> values = g.getValues();
		try {
			return find(hql, values);
		} finally {
			g.dispose();
		}
	}

	public List<T> find(net.uman.framework.modules.search.Criteria criteria) {
		return find(null, criteria);
	}

	public <X> Page<X> findPage(Page<X> page, final List<PropertyFilter> filters) {
		return findPage(page, PropertyFilter.RelationType.AND, filters);
	}
	
	/**
	 * 按属性过滤条件列表分页查找对象.
	 */
	public <X> Page<X> findPage(Page<X> page, PropertyFilter.RelationType rel, final List<PropertyFilter> filters) {
		HQLGenerator g = new HQLGenerator(entityClass, filters, rel);
		g.process();
		String hql = g.getHql();
		Map<String, Object> values = g.getValues();
		try {
			return findPage(page, hql, values);
		} finally {
			g.dispose();
		}
	}

	public <X> Page<X> findPage(Page<X> page, net.uman.framework.modules.search.Criteria criteria) {
		return findPage(page, null, criteria); 
	}
	
	public <X> Page<X> findPage(Page<X> page, List<String> items, net.uman.framework.modules.search.Criteria criteria) {
		HQLGenerator g = new HQLGenerator(items, entityClass, criteria);
		g.process();
		String hql = g.getHql();
		Map<String, Object> values = g.getValues();
		try {
			return findPage(page, hql, values);
		} finally {
			g.dispose();
		}
	}
	
	/**
	 * 判断对象的属性值在数据库内是否唯一.
	 * 
	 * 在修改对象的情景下,如果属性新修改的值(value)等于属性原来的值(orgValue)则不作比较.
	 */
	public boolean isPropertyUnique(final String propertyName, final Object newValue, final Object oldValue) {
		if (newValue == null || newValue.equals(oldValue)) {
			return true;
		}
		Object object = findUniqueBy(propertyName, newValue);
		return (object == null);
	}

	/**
	 * 按HQL分页查询.
	 * 
	 * @param page 分页参数.不支持其中的orderBy参数.
	 * @param hql hql语句.
	 * @param values 数量可变的查询参数,按顺序绑定.
	 * 
	 * @return 分页查询结果, 附带结果列表及所有查询时的参数.
	 */
	@SuppressWarnings("unchecked")
	public <X> List<X> findPage(String hql, int size, Object... values) {
		return findPage(false, hql, size, values);
	}
	
	public <X> List<X> findPageByNativeSql(String hql, int size, Object... values) {
		return findPage(true, hql, size, values);
	}
	
	public <X> List<X> findPageByNamedQuery(String hqlName, int size, Object... values) {
		String hql = SqlHelper.getSql(hqlName);
		boolean nativeSql = SqlHelper.isNativeSql(hqlName);
		return findPage(nativeSql, hql, size, values);
	}
	
	private <X> List<X> findPage(boolean nativeSql, String hql, int size, Object... values) {
		Query q = createQuery(nativeSql, hql, values);

		q.setFirstResult(0);
		q.setMaxResults(size);
		List result = q.list();
		evict(result);
		return result;
	}
	
	@SuppressWarnings("unchecked")
	public void saveWithWraperCode(IdCode idCode){
		buildCode(idCode);
		save((T)idCode);
	}
	/**
	 * @param idCode
	 */
	private void buildCode(IdCode idCode) {
		//TODO parent condition
		if(idCode!=null && idCode.getId()!=null){
			return ;
		}
			
		
		List<T> list=findBy("level", idCode.getLevel());
		Long id=new Long(0);
		
		if(list==null || list.size()<1){
			id=initId(idCode.getLevel(),idCode.getParentId());
		}
		
		for (IdEntity idEntity : list) {
			if(idEntity.getId().longValue()>id.longValue()){
				id=idEntity.getId();
			}
		}
		
		if(checkIdByLevel(id)){
			idCode.setId(id+1);
		}else{
			throw new NullPointerException("编码超出范围，请联系系统管理员！");
		}
	}

	/**
	 * @param level
	 * @param parentId
	 */
	private Long initId(Integer level, Long parentId) {
		if(level.intValue()==1){
			return 10L;
		}else{
			return parentId*100;
		}
	}

	/**
	 * @param id
	 * @param level
	 * @return
	 */
	private boolean checkIdByLevel(Long id) {
		if(id==null){
			return false;
		}
	
		return (id.intValue()%100+1)<100;
	}
	
}
